package com.zys.sac.starter.config;

import cn.hutool.core.util.StrUtil;
import com.zys.sac.core.config.DefaultUrlFilterInvocationSecurityMetadataSource;
import com.zys.sac.core.config.UrlAccessDecisionManager;
import com.zys.sac.core.config.UrlFilterInvocationSecurityMetadataSource;
import com.zys.sac.core.constant.RequestParam;
import com.zys.sac.core.ext.JwtSecurityContext;
import com.zys.sac.core.filter.*;
import com.zys.sac.core.handler.*;
import com.zys.sac.core.helper.*;
import com.zys.sac.core.provider.PlainModeUserAuthenticationProvider;
import com.zys.sac.core.provider.RSAModeUserAuthenticationProvider;
import com.zys.sac.starter.initializer.SacFilterInitializer;
import com.zys.sac.starter.initializer.SacFilterInitializerContext;
import com.zys.sac.starter.initializer.SacSpecialPathInitializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Created by zhongjunkai on 2022/3/15.
 */
@Configuration
@EnableConfigurationProperties({SacPluginProperties.class})
@Import(SacAutoConfigurationAdapter.class)
@PropertySources({@PropertySource(value = "classpath:application.yml", ignoreResourceNotFound = true), @PropertySource(value = "application.properties", ignoreResourceNotFound = true)})
public class SacPluginAutoConfiguration {

    @Autowired
    private SacPluginProperties sacPluginProperties;

    @Bean
    @ConditionalOnMissingBean(AccessDecisionManager.class)
    public AccessDecisionManager accessDecisionManager() {
        return new UrlAccessDecisionManager();
    }

    @Bean
    @ConditionalOnMissingBean(AccessDeniedHandler.class)
    public AccessDeniedHandler accessDeniedHandler() {
        return new SacAccessDeniedHandler();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationEntryPoint.class)
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new SacAuthenticationEntryPoint();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationFailureHandler.class)
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new SacAuthenticationFailureHandler();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationSuccessHandler.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "jwt", matchIfMissing = true)
    public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
        return new SacJwtAuthenticationSuccessHandler();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationSuccessHandler.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "simple", matchIfMissing = false)
    public AuthenticationSuccessHandler simpleAuthenticationSuccessHandler() {
        return new SacJwtAuthenticationSuccessHandler();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationSuccessHandler.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "ssoJwt", matchIfMissing = false)
    public AuthenticationSuccessHandler ssoJwtAuthenticationSuccessHandler() {
        return new SacJwtAuthenticationSuccessHandler();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationSuccessHandler.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "workbench", matchIfMissing = false)
    public AuthenticationSuccessHandler workbenchAuthenticationSuccessHandler() {
        return new SacJwtAuthenticationSuccessHandler();
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "session", matchIfMissing = false)
    public AuthenticationSuccessHandler sessionAuthenticationSuccessHandler() {
        return new SacSessionAuthenticationSuccessHandler();
    }

    @Bean
    @ConditionalOnMissingBean(JwtSecretFactory.class)
    public JwtSecretFactory jwtSecretFactory() {
        return new DefaultJwtSecretFactory();
    }

    @Bean
    @ConditionalOnMissingBean(SacCheckFilter.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "jwt", matchIfMissing = true)
    public SacCheckFilter jwtCheckFilter() {
        JwtCheckFilter jwtCheckFilter = new JwtCheckFilter(sacPluginProperties.getLogin());
        return jwtCheckFilter;
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "session", matchIfMissing = false)
    public SacCheckFilter sessionCheckFilter() {
        SessionCheckFilter sessionCheckFilter = new SessionCheckFilter(sacPluginProperties.getLogin());
        return sessionCheckFilter;
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"authMode", "auth-mode"}, havingValue = "simple", matchIfMissing = false)
    public SacCheckFilter simpleCheckFilter() {
        SimpleCheckFilter simpleCheckFilter = new SimpleCheckFilter(sacPluginProperties.getLogin());
        return simpleCheckFilter;
    }

    @Bean
    @ConditionalOnMissingBean(VerifyCodeFilter.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"enableVerCode", "enable-ver-code"}, havingValue = "true", matchIfMissing = false)
    public VerifyCodeFilter verifyCodeFilter() {
        return new VerifyCodeFilter() {
            // 默认实现，通过Session获取生成的图片验证码
            @Override
            protected Object getGenCode(HttpServletRequest request) {
                return request.getSession().getAttribute(RequestParam.VERIFY_CODE);
            }
        };
    }

    @Bean
    @ConditionalOnMissingBean(LogoutSuccessHandler.class)
    public LogoutSuccessHandler logoutSuccessHandler() {
        return new SacLogoutSuccessHandler();
    }

    @Bean
    @ConditionalOnMissingBean(PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationProvider.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"passwordTransportMode", "password-transport-mode"}, havingValue = "rsa", matchIfMissing = true)
    public AuthenticationProvider sacUserAuthenticationProvider(PasswordEncoder passwordEncoder) {
        return new RSAModeUserAuthenticationProvider(passwordEncoder);
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationProvider.class)
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"passwordTransportMode", "password-transport-mode"}, havingValue = "plain")
    public AuthenticationProvider plainUserAuthenticationProvider(PasswordEncoder passwordEncoder) {
        return new PlainModeUserAuthenticationProvider(passwordEncoder);
    }

    @Bean
    @ConditionalOnMissingBean(SecurityContext.class)
    public SecurityContext jwtSecurityContext() {
        return new JwtSecurityContext();
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"enableMultipleToken", "enable-multiple-token"}, havingValue = "true", matchIfMissing = true)
    @ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
    @ConditionalOnMissingBean(TokenManager.class)
    public TokenManager inMemoryTokenManager() {
        return new InMemoryTokenManager(sacPluginProperties.getTokenCachedTimeInSeconds() * 1000);
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"enableMultipleToken", "enable-multiple-token"}, havingValue = "true", matchIfMissing = true)
    @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate")
    @ConditionalOnMissingBean(TokenManager.class)
    public TokenManager redisTokenManager() {
        return new RedisTokenManager(sacPluginProperties.getTokenCachedTimeInSeconds());
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"enableMultipleToken", "enable-multiple-token"}, havingValue = "false", matchIfMissing = false)
    @ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
    public TokenManager inMemoryMutexTokenManager() {
        return new InMemoryMutexTokenManager(sacPluginProperties.getTokenCachedTimeInSeconds() * 1000);
    }

    @Bean
    @ConditionalOnAnyProperty(prefix = "opensource.sac", name = {"enableMultipleToken", "enable-multiple-token"}, havingValue = "false", matchIfMissing = false)
    @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate")
    public TokenManager redisMutexTokenManager() {
        return new RedisMutexTokenManager(sacPluginProperties.getTokenCachedTimeInSeconds());
    }

    @Bean
    public RSAOperator rsaOperator() {
        String privateKey = sacPluginProperties.getRsaPrivateKey();
        String publicKey = sacPluginProperties.getRsaPublicKey();
        if (StrUtil.isNotBlank(privateKey) && StrUtil.isNotBlank(publicKey)) {
            return new RSAOperator(privateKey, publicKey);
        } else {
            return new RSAOperator();
        }
    }

    @Bean
    public SacFilterInitializerContext sacFilterInitializerContext() {
        return new SacFilterInitializerContext();
    }

    @Bean
    public SacFilterInitializer sacSpecialPathInitializer() {
        return new SacSpecialPathInitializer(sacPluginProperties);
    }

    @Bean
    @ConditionalOnMissingBean(UrlFilterInvocationSecurityMetadataSource.class)
    public UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource() {
        return new DefaultUrlFilterInvocationSecurityMetadataSource();
    }

}
